import schedulecenter
import schedulecenter.timer
import common.globaldefines
import common.configparser
import queue
import os
import sys
import importlib
import threading
import getopt

from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

if "g_App" not in globals():
    g_App = None

def GetModNameByFilePath(filepath: str):
    # 获取文件路径相对于项目根目录的路径
    relativepath = os.path.relpath(filepath, os.getcwd())
    # 去掉 .py 后缀，替换路径分隔符为 .
    modname = relativepath.replace(".py", "").replace(os.sep, ".")
    return modname

class FileChangeHandler(FileSystemEventHandler):

    def on_modified(self, event):
        if not event.src_path.endswith(".py"):
            return
        filepath = event.src_path 
        s = GetModNameByFilePath(filepath)
        g_App.updateModule(s)

def startFileWatcher(dirpath):
    event_handler = FileChangeHandler()
    observer = Observer()
    observer.schedule(event_handler, path=dirpath, recursive=True)
    observer.start()
    return observer


class Application:

    def __init__(self):
        global g_App
        if g_App:
            raise Exception("Application only one instance.")
        g_App = self
        self._img = None
        self._running = True
        self._threadsToMainQueue = queue.Queue(99999)
        self._threads = {}
        self._needupdatemods = set()
        self.createThread("Timer", schedulecenter.timer.g_TimerMgr)
        filepath = os.path.abspath(__file__)
        dirpath = os.path.dirname(filepath)
        self._observer = startFileWatcher(dirpath)

        obj = threading.current_thread()
        setattr(obj, "P_ThreadName", "MainThread")

        self._config = common.configparser.ConfigureParser()
        self._config.loadConfig(os.path.join(os.getcwd(), "app.conf"))

        self._cmdargs = {}

        self._threadInitFinishedCount: int = 0
        self._initFinished: bool = False
        self._initcallbacks = []
        self._screenUpdateCallbacks = []

    @staticmethod
    def instance():
        return g_App

    def parserArgument(self, argsfmt: str = "", argslist = []):
        opts, args = getopt.getopt(sys.argv[1:], argsfmt, argslist)
        for opt, arg in opts:
            self._cmdargs[opt] = arg
        DebugPrint(opts, args)
        DebugPrint(self._cmdargs)

    def getCmdArgs(self, arg, defaultval = None):
        return self._cmdargs.get(arg, defaultval)
    
    def getConfig(self, k, defaultval = None):
        return self._config.get(k, defaultval)

    def updateScreen(self, img):
        self._img = img
        for func in self._screenUpdateCallbacks:
            func(img)

    def updateModule(self, modname: str):
        self._needupdatemods.add(modname)

    def realUpdateModule(self):
        for modname in self._needupdatemods:
            if modname in sys.modules:
                DebugPrint(f"reload mod: {modname}")
                m = sys.modules[modname]
                importlib.reload(m)
            else:
                DebugPrint(f"reload unknow mod: {modname} failed.") 
        self._needupdatemods.clear()

    def onThreadInitFinished(self, threadname: str):
        if self._initFinished:
            return
        self._threadInitFinishedCount += 1
        DebugPrint(f"{threadname} ini finished.")
        if self._threadInitFinishedCount >= len(self._threads):
            self._initFinished = True
            self.onStarted()

    def onStarted(self):
        for cbfunc in self._initcallbacks:
            cbfunc()

    def registerInitCallback(self, cbfunc):
        self._initcallbacks.append(cbfunc)

    def handleQueueMessage(self):
        times = 9999
        while times:
            times -= 1
            if self._threadsToMainQueue.empty():
                break
            tarthreadname, srcthreadname, *args = self._threadsToMainQueue.get_nowait()
            #DebugPrint(f"Message [{srcthreadname}] -> [{tarthreadname}] args: {args}")
            if tarthreadname == "MainThread":
                op = args[0]
                if "Start" == op:
                    self.onThreadInitFinished(srcthreadname)
            elif tarthreadname in self._threads:
                threadobj: common.globaldefines.ThreadBase = self._threads[tarthreadname]
                threadobj.pushToThread(srcthreadname, args)
            
            # post to thread

    def handleThreadMessage(self):
        for v in self._threads.values():
            v.frame()

    def pushMessage(self, threadname: str, *args):
        selfthreadname = common.globaldefines.GetCurrentThreadName()
        self._threadsToMainQueue.put_nowait((threadname, selfthreadname, *args))

    def createThread(self, name: str, threadobj: common.globaldefines.ThreadBase):
        if name in self._threads:
            raise Exception(f"thread \"{name}\" already exists.")
        self._threads[name] = threadobj
        threadobj._hookOnStarted = self.threadOnStarted
        threadobj.setThreadProperty("ThreadName", name)
        threadobj.start()

    def threadOnStarted(self):
        # run in thread
        self.pushMessage("MainThread", "Start")

    def getThread(self, name: str):
        if name in self._threads:
            return self._threads[name]
        return None

    # delay: second
    def startTimer(self, delay, cbfunc):
        self.pushMessage("Timer", delay, cbfunc)

    def frame(self):
        self.handleThreadMessage()
        self.handleQueueMessage()
        self.realUpdateModule()

    def exec(self):
        while self._running:
            self.frame()
        for v in self._threads.values():
            v.stop()

    def exit(self):
        self._running = False
